import Konva from 'konva'
import { formatImgWH, formatNumber, loadByFrameAsync } from 'src/utils/utils'
import { getModelImg } from 'src/views/MapCore/MapGlobal/utils/utils.mqtt.js'
import { userStores } from 'stores/user-stores'
import {
  backwardCoord, batchCreatePoint, calculateCoord, createText, destroyKonva, drawAreaByCoord, drawLineByCoord,
  fillUndoList, handleSelectedLines, handleSelectedPoints, initZoomCanvas, mapToolsBar, mousedownEvent, mouseenterEvent,
  mousemoveEvent, mouseupEvent, moveSLAM, zoomCanvas
} from 'src/views/MapCore/MapGlobal/utils/MouseEvent'
import {
  formatImportArea, formatImportLine, formatImportPoint, formatImportText, formatPositionType
} from 'src/views/MapCore/MapGlobal/utils/utils'
import { ref } from 'vue'

let CANVAS, operShape, drawPoint
const userStore = userStores()
const IMGSIZE = 0.5, pointMaps = new Map()
const batchSelected = [], redoList = [], undoList = []
const isDraw = ref(false)
const curveInfo = ref({})
export const canRedo = ref(false)
export const canUndo = ref(false)
export const amrPanel = ref({})
export const attrsModal = ref(false)
export const shapeAttrs = ref({})
export const tipsPointer = ref({})
export const tipPosition = ref({})
export const samplingInfo = ref({})
export const showRightMenu = ref(false)
export const currentAction = ref('move')
export const isContinuousDot = ref(false)
export const showPointerTips = ref(false)
export const contextMenuInfo = ref({})
const cachePoint = new Konva.Image(
  { width: IMGSIZE, height: IMGSIZE, image: null, imageSmoothingEnabled: false, rotation: 0 }).cache()

export const setOperShape = shape => (operShape = shape)
export const setCanRedo = bool => (canRedo.value = !!bool)
export const setCanUndo = bool => (canUndo.value = !!bool)
export const setCurveInfo = info => (curveInfo.value = info)
export const setAttrsModal = bool => (attrsModal.value = !!bool)
export const setShowRightMenu = bool => (showRightMenu.value = !!bool)
export const setShowPointerTips = bool => (showPointerTips.value = !!bool)
export const setIsContinuousDot = bool => (isContinuousDot.value = !!bool)
export const setCurrentAction = action => {
  action && (currentAction.value = action)
  CANVAS.draggable(action === 'move')
  moveSLAM(CANVAS, action === 'moveSLAM')
  attrsModal.value && setAttrsModal(false)
  showRightMenu.value && setShowRightMenu(false)
  if (batchSelected.length || operShape) clearSelected()
}
export const exportAttrs = () => ({ CANVAS, operShape, drawPoint, batchSelected, redoList, undoList })
export const setDrawPoint = async pointType => (drawPoint = await fillDrawPoint(pointType, 'point'))

export const fillDrawPoint = async (pointType, action) => {
  return new Promise(resolve => {
    if (pointMaps.has(pointType)) {
      if (action) setCurrentAction(action)
      return resolve(pointMaps.get(pointType).clone())
    }
    const image = new Image(), positionImageUrl = formatPositionType(pointType)
    image.src = `${userStore.OSSPath}/${positionImageUrl}`
    image.onload = () => {
      const { width, height } = formatImgWH(image, IMGSIZE)
      const pointClone = cachePoint.clone(
        { width, height, image, pointType, offsetX: width * 0.5, offsetY: height * 0.5 })
      pointMaps.set(pointType, pointClone.clone())
      if (action) setCurrentAction(action)
      resolve(pointClone)
    }
    image.onerror = () => {
      image.src = new URL('../assets/LoadFail.png', import.meta.url).href
      const width = IMGSIZE, height = IMGSIZE
      const pointClone = cachePoint.clone(
        { width, height, image, pointType, offsetX: width * 0.5, offsetY: height * 0.5 })
      pointMaps.set(pointType, pointClone.clone())
      if (action) setCurrentAction(action)
      resolve(pointClone)
    }
  })
}
export const addDotMatrix = async (point, dotMatrixInfo, done) => {
  const formatPoint = await fillDrawPoint(point.positionPackageCode, 'move')
  const points = await batchCreatePoint(CANVAS, formatPoint, dotMatrixInfo)
  setCanUndoRedo(points, 'createPoint')
  done && done()
}
const getMinMaxValue = ({ minX, minY, maxX, maxY }, x, y) => {
  minX = Math.min(minX ?? x, x)
  minY = Math.min(minY ?? y, y)
  maxX = Math.max(maxX ?? x, x)
  maxY = Math.max(maxY ?? y, y)
  return { minX, minY, maxX, maxY }
}
const updateLayer = (name = '', listening = false) => {
  let shapeLayer = CANVAS.findOne(`.${name}`)
  if (!shapeLayer) {
    shapeLayer = new Konva.Layer({ name, listening })
    CANVAS.add(shapeLayer)
  }
  return shapeLayer
}
const updateGroup = (name = '', removeChildren = false, layer, listening = false) => {
  let shapeGroup = layer.findOne(`.${name}`)
  if (!shapeGroup) {
    shapeGroup = new Konva.Group({ name, listening })
    layer.add(shapeGroup)
  } else removeChildren && shapeGroup.removeChildren()
  return shapeGroup
}
export const openedAttrsDrawer = (action) => {
  if (!operShape && !batchSelected.length) return
  const mapGroundCode = CANVAS.name()
  if (batchSelected.length > 1) {
    shapeAttrs.value = { action, mapGroundCode }
    setAttrsModal(true)
    setShowRightMenu(false)
    return
  }
  setOperShape(operShape || batchSelected[0])
  if (action === 'line') {
    const uid = operShape.getAttr('uid')
    operShape = CANVAS.findOne('.lines').findOne(el => el.getAttr('uid') === uid)
    if (!operShape) return
  }
  const { x = 0, y = 0, width = 0, height = 0, ...params } = operShape.getAttrs()
  params.image && delete params.image
  shapeAttrs.value = { action, x, y, width: Math.abs(width), height: Math.abs(height), mapGroundCode, ...params }
  setAttrsModal(true)
  setShowRightMenu(false)
}
export const initCanvas = (name = '', callback) => {
  if (!name) return CANVAS
  if (!CANVAS) {
    CANVAS = new Konva.Stage({ container: `.${name}`, draggable: true, name })
    callback && callback(CANVAS)
    onInitCanvasEvent()
    return CANVAS
  }
  if (CANVAS.name() === name) return CANVAS
  offInitCanvasEvent()
  CANVAS.removeChildren() && CANVAS.destroy()
  CANVAS = new Konva.Stage({ container: `.${name}`, draggable: true, name })
  callback && callback(CANVAS)
  onInitCanvasEvent()
  return CANVAS
}
export const updateCanvas = (removeChildren = false) => {
  const shapeLayer = updateLayer('shapeLayer', true)
  const lineGroup = updateGroup('lines', removeChildren, shapeLayer)
  const arrowGroup = updateGroup('arrows', removeChildren, shapeLayer, true)
  const pointGroup = updateGroup('points', removeChildren, shapeLayer, true)
  const areaGroup = updateGroup('areas', removeChildren, shapeLayer)
  const textGroup = updateGroup('texts', removeChildren, shapeLayer, true)
  const selectedGroup = updateGroup('selectedShapes', removeChildren, shapeLayer)
  selectedGroup && selectedGroup.moveToTop()
  return { lineGroup, arrowGroup, pointGroup, areaGroup, textGroup, selectedGroup }
}
export const resetCanvas = async ({ action = '', pointGroup, isZoom = true }, points = []) => {
  if (!CANVAS) return
  let XYInterval = {}
  if (action !== 'formatImg') {
    for (const point of points) {
      const { x, y } = point.position()
      XYInterval = getMinMaxValue(XYInterval, x, y)
    }
    return initZoomCanvas(CANVAS, XYInterval)
  }
  if (!pointGroup) return initZoomCanvas(CANVAS)
  if (!pointGroup) pointGroup = CANVAS.findOne('.points')
  await loadByFrameAsync(points, 500, async pointList => {
    for (const point of pointList) {
      const { x, y, pointType, ...params } = formatImportPoint(point)
      if (isZoom) XYInterval = getMinMaxValue(XYInterval, x, y)
      const pointImg = await fillDrawPoint(pointType), scaleX = mapToolsBar.pointZoom || 1
      pointGroup.add(pointImg.clone({ scaleX, scaleY: -scaleX, ...params, x, y }))
    }
  })
  isZoom && initZoomCanvas(CANVAS, XYInterval)
}
export const loadMap = async (mapDetails = {}, PreCallback, PostCallback) => {
  PreCallback && PreCallback()
  CANVAS && CANVAS.hide()
  const {
    baseMapAreas = [], baseMapPaths = [], baseMapLocations = [], baseMapWords = [],
    isZoom = true, nonAreaAndText = false
  } = mapDetails
  const { pointGroup } = updateCanvas(true)
  const params = { action: 'formatImg', pointGroup, isZoom }
  await loadByFrameAsync(baseMapPaths, 500, lines => {
    for (const line of lines) drawLineByCoord(CANVAS, formatImportLine(line))
  })
  if (!nonAreaAndText) {
    for (const word of baseMapWords) createText(CANVAS, formatImportText(word))
    for (const area of baseMapAreas) drawAreaByCoord(CANVAS, formatImportArea(area))
  }
  await resetCanvas(params, baseMapLocations, PostCallback)
  CANVAS.findOne('.shapeLayer').batchDraw()
  PostCallback && PostCallback()
}
export const offInitCanvasEvent = () => CANVAS && CANVAS.off('wheel dragmove dblclick mouseenter')
export const onInitCanvasEvent = () => {
  CANVAS.on('wheel', async e => {
    e.evt.preventDefault()
    if (showRightMenu.value) return
    if (showPointerTips.value) {
      setShowPointerTips(false)
      setTimeout(() => {
        tipPosition.value = backwardCoord(CANVAS, tipsPointer.value)
        setShowPointerTips(true)
      }, 0)
    }
    zoomCanvas(e, CANVAS)
  })
  CANVAS.on('dragmove', () => showPointerTips.value && setShowPointerTips(false))
  CANVAS.on('mouseenter', () => mouseenterEvent(currentAction.value, CANVAS))
  CANVAS.on('dblclick', e => {
    e.evt.preventDefault()
    clearSelected()
    amrPanel.value = { visible: false }
  })
}
export const offInitEvent = (groups = {}) => {
  if (!CANVAS) return
  const arrowGroup = groups.arrowGroup || CANVAS.findOne('.arrows')
  const pointGroup = groups.pointGroup || CANVAS.findOne('.points')
  const textGroup = groups.textGroup || CANVAS.findOne('.texts')
  textGroup && textGroup.off('mouseenter mousemove mouseout')
  arrowGroup && arrowGroup.off('mouseenter mousemove mouseout')
  pointGroup && pointGroup.off('mouseenter mousemove mouseout')
}
export const onInitEvent = () => {
  if (!CANVAS) return
  const { arrowGroup, pointGroup, textGroup } = updateCanvas(void 0)
  offInitEvent({ arrowGroup, pointGroup, textGroup })
  if (!pointGroup) return
  pointGroup.on('mouseenter mousemove', e => {
    if (showRightMenu.value) return
    const { x, y } = tipsPointer.value
    if (showPointerTips.value && x === e.target.x() && y === e.target.y()) return
    tipsPointer.value = e.target.getAttrs()
    tipPosition.value = backwardCoord(CANVAS, tipsPointer.value)
    setShowPointerTips(true)
    if (['move'].includes(currentAction.value)) CANVAS.container().style.cursor = 'pointer'
  })
  pointGroup.on('mouseout', () => {
    setShowPointerTips(false)
    if (['move'].includes(currentAction.value)) CANVAS.container().style.cursor = 'move'
  })
  if (!arrowGroup) return
  arrowGroup.on('mouseenter mousemove', () => {
    if (showRightMenu.value) return
    if (['move'].includes(currentAction.value)) CANVAS.container().style.cursor = 'pointer'
  })
  arrowGroup.on('mouseout', () => {
    if (['move'].includes(currentAction.value)) CANVAS.container().style.cursor = 'move'
  })
  if (!textGroup) return
  textGroup.on('mouseenter mousemove', () => {
    if (showRightMenu.value) return
    if (['move'].includes(currentAction.value)) CANVAS.container().style.cursor = 'pointer'
  })
  textGroup.on('mouseout', () => {
    if (['move'].includes(currentAction.value)) CANVAS.container().style.cursor = 'move'
  })
}
export const offOperEvent = (groups = {}) => {
  if (!CANVAS) return
  const arrowGroup = groups.arrowGroup || CANVAS.findOne('.arrows')
  const pointGroup = groups.pointGroup || CANVAS.findOne('.points')
  const textGroup = groups.textGroup || CANVAS.findOne('.texts')
  textGroup && textGroup.off('contextmenu dblclick')
  arrowGroup && arrowGroup.off('contextmenu dblclick')
  pointGroup && pointGroup.off('dblclick mousedown mouseup contextmenu')
  CANVAS && CANVAS.draggable(true) && CANVAS.off('click mousedown mousemove mouseup contextmenu')
}
export const onOperEvent = () => {
  if (!CANVAS) return
  const { areaGroup, arrowGroup, pointGroup, textGroup } = updateCanvas(void 0)
  offOperEvent({ arrowGroup, pointGroup, textGroup })
  pointGroup.on('mousedown', async e => {
    if (!['line', 'curve'].includes(currentAction.value)) return
    e.cancelBubble = true
    if (operShape && operShape.getAttrs().type === 'line') {
      isDraw.value = false
      mouseupEvent(e, currentAction.value, CANVAS, operShape, batchSelected, curveInfo.value)
      operShape && setOperShape(void 0)
      return
    }
    setOperShape(await mousedownEvent(e, currentAction.value, CANVAS))
    isDraw.value = true
  })
  pointGroup.on('click', e => {
    if (samplingInfo.value.action !== 'point') return
    e.cancelBubble = true
    if (batchSelected.find(el => el.getAttr('positionCode') === e.target.getAttr('positionCode'))) return
    batchSelected.push(e.target)
    handleSelectedPoints(CANVAS, batchSelected)
    samplingInfo.value.samplings = batchSelected.length
  })
  pointGroup.on('mouseup', e => {
    if (!isDraw.value || !operShape || !['line', 'curve'].includes(currentAction.value)) return
    e.cancelBubble = true
    const lines = mouseupEvent(e, currentAction.value, CANVAS, operShape, [], curveInfo.value)
    isDraw.value = false
    operShape && setOperShape(void 0)
    setCanUndoRedo(lines, 'createLine')
  })
  pointGroup.on('contextmenu', e => {
    if (['move'].includes(currentAction.value)) e.cancelBubble = true
    else return
    setShowPointerTips(false)
    clearSelected()
    setOperShape(e.target)
    batchSelected.push(e.target)
    handleSelectedPoints(CANVAS, batchSelected)
    contextMenuInfo.value = { type: 'point', x: e.evt.offsetX, y: e.evt.offsetY }
    setShowRightMenu(true)
  })
  pointGroup.on('dblclick', e => {
    e.cancelBubble = true
    if (!['move'].includes(currentAction.value) || samplingInfo.value.action === 'point') return
    clearSelected()
    setOperShape(e.target)
    batchSelected.push(e.target)
    handleSelectedPoints(CANVAS, batchSelected)
    openedAttrsDrawer('point')
  })
  arrowGroup.on('contextmenu', e => {
    if (['move'].includes(currentAction.value)) e.cancelBubble = true
    else return
    setShowPointerTips(false)
    clearSelected([e.target])
    setOperShape(e.target)
    batchSelected.push(e.target)
    contextMenuInfo.value = { type: 'line', x: e.evt.offsetX, y: e.evt.offsetY }
    setShowRightMenu(true)
  })
  arrowGroup.on('dblclick', e => {
    e.cancelBubble = true
    if (!['move'].includes(currentAction.value)) return
    clearSelected([e.target])
    setOperShape(e.target)
    batchSelected.push(e.target)
    openedAttrsDrawer('line')
  })
  textGroup.on('dblclick', e => {
    e.cancelBubble = true
    if (!['move'].includes(currentAction.value)) return
    setShowPointerTips(false)
    const { type, uid } = e.target.getAttrs()
    clearSelected()
    if (type === 'area') {
      setOperShape(areaGroup.find('Rect').find(el => el.getAttr('uid') === uid))
      if (operShape) openedAttrsDrawer(type)
    } else if (type === 'text') {
      setOperShape(e.target)
      openedAttrsDrawer(type)
    }
  })
  textGroup.on('contextmenu', e => {
    e.cancelBubble = true
    if (!['move'].includes(currentAction.value)) return
    setShowPointerTips(false)
    const { type, uid } = e.target.getAttrs()
    if (type === 'area') {
      setOperShape(areaGroup.find('Rect').find(el => el.getAttr('uid') === uid))
      if (!operShape) return
    } else if (type === 'text') setOperShape(e.target)
    contextMenuInfo.value = { type, x: e.evt.offsetX, y: e.evt.offsetY }
    setShowRightMenu(true)
  })
  CANVAS.on('mousedown', async e => {
    if (showRightMenu.value) {
      setOperShape(void 0)
      setShowRightMenu(false)
    }
    if (['pointSelection', 'lineSelection'].includes(currentAction.value) && attrsModal.value) {
      setOperShape(void 0)
      setAttrsModal(false)
    }
    if (['line', 'curve', 'move', 'moveSLAM'].includes(currentAction.value)) return
    if (['text'].includes(currentAction.value)) {
      shapeAttrs.value = { action: 'createText', ...calculateCoord(CANVAS) }
      setAttrsModal(true)
      return
    }
    if (!['point'].includes(currentAction.value) && operShape) {
      isDraw.value = false
      mouseupEvent(e, currentAction.value, CANVAS, operShape, batchSelected, curveInfo.value)
      setOperShape(void 0)
      return
    }
    setOperShape(await mousedownEvent(e, currentAction.value, CANVAS, drawPoint))
    if (currentAction.value === 'point') {
      operShape && setCanUndoRedo([operShape], 'createPoint') && setOperShape(void 0)
      if (!isContinuousDot.value) mouseenterEvent('move', CANVAS)
      return
    }
    if (['pointSelection', 'lineSelection', 'area'].includes(currentAction.value)) isDraw.value = true
  })
  CANVAS.on('mousemove', e => {
    if (!isDraw.value || !operShape) return
    if (!['line', 'curve', 'pointSelection', 'lineSelection', 'area'].includes(currentAction.value)) return
    mousemoveEvent(e, currentAction.value, CANVAS, operShape)
  })
  CANVAS.on('mouseup', e => {
    // 此处定时器仅为解决win电脑中，点击鼠标右键时，先走此事件
    let upTimer = setTimeout(() => {
      clearTimeout(upTimer)
      upTimer = null
      if (currentAction.value === 'point' && !isContinuousDot.value) setCurrentAction('move')
      if (!isDraw.value || !operShape) return
      isDraw.value = false
      if (currentAction.value === 'area') {
        const { x = 0, y = 0, width = 0, height = 0, uid } = operShape.getAttrs()
        if (!width || !height) {
          operShape && operShape.remove() && setOperShape(void 0)
          return
        }
        if (width < 0) {
          operShape.width(Math.abs(width))
          operShape.x(formatNumber(x + width))
        }
        if (height > 0) {
          operShape.height(-height)
          operShape.y(formatNumber(y + height))
        }
        shapeAttrs.value = {
          x: operShape.x(), y: operShape.y(), width: Math.abs(width), height: Math.abs(height), uid,
          action: 'createArea'
        }
        setAttrsModal(true)
        return
      }
      mouseupEvent(e, currentAction.value, CANVAS, operShape, batchSelected, curveInfo.value)
      operShape && setOperShape(void 0)
    }, 0)
  })
  CANVAS.on('contextmenu', e => {
    e.cancelBubble = true
    if (!['pointSelection', 'lineSelection'].includes(currentAction.value)) return
    isDraw.value = false
    setShowPointerTips(false)
    if (batchSelected.length) {
      const clsName = batchSelected[0].getClassName()
      if (clsName === 'Line' && currentAction.value === 'pointSelection') clearSelected()
      else if (clsName === 'Image' && currentAction.value === 'lineSelection') clearSelected()
    }
    operShape && operShape.remove() && setOperShape(void 0)
    contextMenuInfo.value = { x: e.evt.offsetX, y: e.evt.offsetY }
    setShowRightMenu(true)
  })
  CANVAS.on('click', 'Stage', () => {
    if (!['move'].includes(currentAction.value)) return
    clearSelected()
    setAttrsModal(false)
    setShowRightMenu(false)
  })
}
export const clearSelected = targets => {
  if (!CANVAS) return
  const selectedList = Array.isArray(targets) ? targets : []
  handleSelectedLines(CANVAS, batchSelected, selectedList)
  handleSelectedPoints(CANVAS, batchSelected, true)
  batchSelected.splice(0)
  operShape && setOperShape(void 0)
}
export const clearOperShape = action => {
  if (operShape) {
    if (action === 'deleteArea') operShape.remove()
    setOperShape(void 0)
  }
  if (action !== 'move') return
  setCurrentAction(action)
  mouseenterEvent(action, CANVAS)
}
export const setCanUndoRedo = (operList = [], operType = '', isClear) => {
  if (isClear) {
    setCanRedo(false)
    setCanUndo(false)
    undoList.splice(0)
    redoList.splice(0)
    clearSelected()
    return
  }
  fillUndoList(undoList, redoList, operList, operType)
  setCanUndo(undoList.length > 0)
  setCanRedo(redoList.length > 0)
}
export const loadAlarmAMRs = async (alarmAMRs = []) => {
  if (!alarmAMRs || !alarmAMRs.length) return
  const AMRLayer = updateLayer('AMRs')
  AMRLayer.moveToTop()
  for (const AMR of alarmAMRs) {
    AMR.posX = Number(AMR.posX) / 1000
    AMR.posY = Number(AMR.posY) / 1000
    AMR.amrStatus = 'ERROR'
    const group = await getModelImg(CANVAS, AMR, AMR.agvCode)
    AMRLayer.add(group)
  }
  AMRLayer.batchDraw()
}

export const destroyCanvas = () => {
  CANVAS && CANVAS.removeChildren()
  CANVAS && CANVAS.destroy()
  destroyKonva()
  CANVAS = null
}